﻿namespace Algo.Tests
{
    using System;
    using System.Collections.Generic;

    using Algo.Search;

    using Moq;

    using NUnit.Framework;

    using Shouldly;

    [TestFixture]
    public class DichotomySearchStrategyFixture
    {
        private readonly IList<int> _items = Array.AsReadOnly(new[] { 1, 2, 3, 5, 6 });
        private Mock<IMetricsExtension> _metrics;
        private Mock<IAlgoExtensions> _extensions;
        private int _iterationsCount;

        [SetUp]
        public void SetUp()
        {
            _iterationsCount = 0;
            _metrics = new Mock<IMetricsExtension>();
            _metrics.Setup(i => i.RegisterIterations(It.IsAny<int>()))
                .Callback<int>(
                    count =>
                    {
                        lock (_metrics)
                        {
                            _iterationsCount += count;
                        }
                    });

            var sortingMetricsObject = _metrics.Object;
            _extensions = new Mock<IAlgoExtensions>();
            _extensions.Setup(i => i.TryGet(out sortingMetricsObject)).Returns(true);
        }

        [Test]
        [TestCase(typeof(BinarySearchStrategy))]
        [TestCase(typeof(GoldenSectionSearchStrategy))]
        public void ShouldFindWhenExists(Type type)
        {
            // Given
            var target = CreateInstance(type);

            // When
            int? prev;
            int? next;
            var finded = target.Find(_items, 3, Comparer<int>.Default, _extensions.Object, out prev, out next);

            // Then
            finded.ShouldBe(true);
            prev.ShouldBe(2);
            next.ShouldBe(2);
            _iterationsCount.ShouldBe(2);
        }

        [Test]
        [TestCase(typeof(BinarySearchStrategy))]
        [TestCase(typeof(GoldenSectionSearchStrategy))]
        public void ShouldFindWhenNotExists(Type type)
        {
            // Given
            var target = CreateInstance(type);

            // When
            int? prev;
            int? next;
            var finded = target.Find(_items, 4, Comparer<int>.Default, _extensions.Object, out prev, out next);

            // Then
            finded.ShouldBe(false);
            prev.ShouldBe(2);
            next.ShouldBe(3);
            _iterationsCount.ShouldBe(2);
        }

        [Test]
        [TestCase(typeof(BinarySearchStrategy))]
        [TestCase(typeof(GoldenSectionSearchStrategy))]
        public void ShouldFindWhenFirst(Type type)
        {
            // Given
            var target = CreateInstance(type);

            // When
            int? prev;
            int? next;
            var finded = target.Find(_items, 1, Comparer<int>.Default, _extensions.Object, out prev, out next);

            // Then
            finded.ShouldBe(true);
            prev.ShouldBe(0);
            next.ShouldBe(0);
            _iterationsCount.ShouldBe(1);
        }

        [Test]
        [TestCase(typeof(BinarySearchStrategy))]
        [TestCase(typeof(GoldenSectionSearchStrategy))]
        public void ShouldFindWhenBeforeFirst(Type type)
        {
            // Given
            var target = CreateInstance(type);

            // When
            int? prev;
            int? next;
            var finded = target.Find(_items, 0, Comparer<int>.Default, _extensions.Object, out prev, out next);

            // Then
            finded.ShouldBe(false);
            prev.ShouldBe(null);
            next.ShouldBe(0);
            _iterationsCount.ShouldBe(1);
        }

        [Test]
        [TestCase(typeof(BinarySearchStrategy))]
        [TestCase(typeof(GoldenSectionSearchStrategy))]
        public void ShouldFindWhenLast(Type type)
        {
            // Given
            var target = CreateInstance(type);

            // When
            int? prev;
            int? next;
            var finded = target.Find(_items, 6, Comparer<int>.Default, _extensions.Object, out prev, out next);

            // Then
            finded.ShouldBe(true);
            prev.ShouldBe(4);
            next.ShouldBe(4);
            _iterationsCount.ShouldBe(1);
        }

        [Test]
        [TestCase(typeof(BinarySearchStrategy))]
        [TestCase(typeof(GoldenSectionSearchStrategy))]
        public void ShouldFindWhenAfterLast(Type type)
        {
            // Given
            var target = CreateInstance(type);

            // When
            int? prev;
            int? next;
            var finded = target.Find(_items, 10, Comparer<int>.Default, _extensions.Object, out prev, out next);

            // Then
            finded.ShouldBe(false);
            prev.ShouldBe(4);
            next.ShouldBe(null);
            _iterationsCount.ShouldBe(1);
        }

        [Test]
        [TestCase(typeof(BinarySearchStrategy))]
        [TestCase(typeof(GoldenSectionSearchStrategy))]
        public void ShouldFindWhenEmpty(Type type)
        {
            // Given
            var target = CreateInstance(type);

            // When
            int? prev;
            int? next;
            var finded = target.Find(new int[0], 1, Comparer<int>.Default, _extensions.Object, out prev, out next);

            // Then
            finded.ShouldBe(false);
            prev.ShouldBe(null);
            next.ShouldBe(null);
            _iterationsCount.ShouldBe(0);
        }

        private static IDichotomySearchStrategy CreateInstance(Type type)
        {
            return (IDichotomySearchStrategy)Activator.CreateInstance(type);
        }
    }
}
